package com.nineclock.attendance.service.impl;
import java.io.IOException;
import java.math.BigDecimal;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.google.common.collect.Lists;

import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.nineclock.attendance.dto.AttendGroupDTO;
import com.nineclock.attendance.dto.AttendPunchDTO;
import com.nineclock.attendance.dto.AttendPunchUserWholeDayDTO;
import com.nineclock.attendance.dto.AttendsDTO;
import com.nineclock.attendance.enums.AttendEnums;
import com.nineclock.attendance.excel.CustomHandler;
import com.nineclock.attendance.mapper.AttendPunchMapper;
import com.nineclock.attendance.pojo.AttendPunch;
import com.nineclock.attendance.service.AttendGroupService;
import com.nineclock.attendance.service.AttendPunchService;
import com.nineclock.common.enums.ResponseEnum;
import com.nineclock.common.exception.NcException;
import com.nineclock.common.threadlocal.CurrentUserHolder;
import com.nineclock.common.utils.BeanHelper;
import com.nineclock.common.utils.DateTimeUtil;
import com.nineclock.common.utils.DistanceUtil;
import com.nineclock.system.dto.SysCompanyUserDTO;
import com.nineclock.system.feign.SysCompanyUserFeign;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Transactional
public class AttendPunchServiceImpl implements AttendPunchService {
    @Autowired
    private AttendGroupService attendGroupService;
    @Autowired
    private AttendPunchMapper attendPunchMapper;
    @Autowired
    private SysCompanyUserFeign companyUserFeign;


    @Override
    public void punch(AttendPunchDTO attendPunchDTO) {
        // 1.参数校验
        if (attendPunchDTO == null) {
            throw new NcException(ResponseEnum.INVALID_PARAM_ERROR);
        }
        // 2.封装打卡信息
        AttendPunch attendPunch = new AttendPunch();
        // 2.1 设置基本信息，员工id、公司id、创建时间
        attendPunch.setCompanyUserId(CurrentUserHolder.get().getCompanyUserId());
        attendPunch.setCompanyId(CurrentUserHolder.get().getCompanyId());
        attendPunch.setCreateTime(new Date());
        // 2.2 设置打卡时间（以服务端为准，否则用户可以在客户端修改手机时间打卡）
        Date date = new Date();
        attendPunch.setPunchTime(date);
        attendPunch.setPunchDateStr(DateTimeUtil.dateToStr(date,DateTimeUtil.TIME_FORMAT_2));
        // 2.3 设置上午、下午打卡（1-上午，2-下午）
        attendPunch.setPunchOnOffWork(DateTimeUtil.noonType(date) + 1);
        // 2.4 查询员工所属的考勤组信息
        AttendGroupDTO attendGroupDTO = attendGroupService.getAttendGroupByUserId();
        if (attendGroupDTO == null){
            throw new NcException(ResponseEnum.USER_NOT_MATCH_ATTENDGROUP);
        }
        // 2.5 判断是否有效范围内打卡，非有效范围抛出异常
        boolean location = this.checkLocation(attendPunchDTO, attendGroupDTO);
        if (!location){
            throw new NcException(ResponseEnum.PUNCH_INVALID_AREA);
        }
        attendPunch.setAreaValid(location);
        // 2.6 判断是否是工作日打卡，非工作日抛出异常
        boolean workday = this.checkWorkDays(attendGroupDTO);
        if (!workday){
            throw new NcException(ResponseEnum.PUNCH_INVALID_DAY);
        }
        // 2.7 设置打卡类型（打卡类型 1正常；2迟到；3早退；4旷工）
        this.setPunchType(attendPunch,attendGroupDTO);
        // 2.8 设置是否为有效打卡（上午以最早一次为有效，下午以最晚一次为有效）
        this.setEnable(attendPunch);
        // 2.9 打卡来源
        attendPunch.setPunchSource(AttendEnums.PUNCH_SOURCE_PUNCH.value());
        attendPunch.setLat(attendPunchDTO.getLat());
        attendPunch.setLng(attendPunchDTO.getLng());
        attendPunch.setAddress(attendPunchDTO.getAddress());
        attendPunchMapper.insert(attendPunch);
    }

    @Override
    public AttendPunchUserWholeDayDTO get() {
        AttendPunchUserWholeDayDTO attendPunchUserWholeDayDTO = new AttendPunchUserWholeDayDTO();
        String nowStr = DateTimeUtil.dateToStr(new Date(), DateTimeUtil.TIME_FORMAT_2);
        LambdaQueryWrapper<AttendPunch> wrapperMorning = new LambdaQueryWrapper<>();
        wrapperMorning.eq(AttendPunch::getEffectiveValid,true);
        wrapperMorning.eq(AttendPunch::getCompanyUserId,CurrentUserHolder.get().getCompanyUserId());
        wrapperMorning.eq(AttendPunch::getCompanyId,CurrentUserHolder.get().getCompanyId());
        wrapperMorning.eq(AttendPunch::getPunchOnOffWork,AttendEnums.NOON_TYPE_MORNING.value());
        wrapperMorning.eq(AttendPunch::getPunchDateStr,nowStr);
        AttendPunch attendPunchMorning = attendPunchMapper.selectOne(wrapperMorning);
        attendPunchUserWholeDayDTO.setAttendPunchMorningResponse(BeanHelper.copyProperties(attendPunchMorning,AttendPunchDTO.class));

        LambdaQueryWrapper<AttendPunch> wrapperAfterNoon = new LambdaQueryWrapper<>();
        wrapperAfterNoon.eq(AttendPunch::getEffectiveValid,true);
        wrapperAfterNoon.eq(AttendPunch::getCompanyUserId,CurrentUserHolder.get().getCompanyUserId());
        wrapperAfterNoon.eq(AttendPunch::getCompanyId,CurrentUserHolder.get().getCompanyId());
        wrapperAfterNoon.eq(AttendPunch::getPunchOnOffWork,AttendEnums.NOON_TYPE_AFTERNOON.value());
        wrapperAfterNoon.eq(AttendPunch::getPunchDateStr,nowStr);
        AttendPunch attendPunchAfternoon = attendPunchMapper.selectOne(wrapperAfterNoon);
        attendPunchUserWholeDayDTO.setAttendPunchAfterNoonResponse(BeanHelper.copyProperties(attendPunchAfternoon,AttendPunchDTO.class));
        return attendPunchUserWholeDayDTO;
    }

    @Override
    public List<AttendPunchDTO> members(String startTime, String endTime) {
        List<String> dateList = DateTimeUtil.getSpanDateList(startTime, endTime);
        List<SysCompanyUserDTO> companyUserDTOList = companyUserFeign.queryAllUser().getData();
        if (!CollectionUtils.isEmpty(companyUserDTOList)) {
            // 3.查询每个员工每天的打卡情况
            // 3.1 遍历企业员工列表
            return companyUserDTOList.stream().map(sysCompanyUserDTO -> {
                // 3.2 封装用户基本信息
                AttendPunchDTO attendPunchDTO = BeanHelper.copyProperties(sysCompanyUserDTO, AttendPunchDTO.class);

                // 3.3 获取该用户的指定时间范围内的有效打卡数据
                LambdaQueryWrapper<AttendPunch> wrapperPunch = new LambdaQueryWrapper<>();
                wrapperPunch.eq(AttendPunch::getCompanyUserId,sysCompanyUserDTO.getId());
                wrapperPunch.between(AttendPunch::getPunchDateStr,startTime,endTime);
                wrapperPunch.eq(AttendPunch::getEffectiveValid,true);

                // 3.3.1 包含该员工上午及下午的有效打卡数据（每天的有效打卡数据最多两条）
                List<AttendPunch> attendPunchList = attendPunchMapper.selectList(wrapperPunch);
                // 3.3.2 将List转为Map<日期，这日期对应的有效打卡数据>，其中key为punchDateStr
                Map<String,List<AttendPunch>> puchMap = attendPunchList.stream().collect(Collectors.groupingBy(AttendPunch::getPunchDateStr));
                // 3.4 封装打卡数据，每天都生成一个AttendsDTO，返回一个集合
                List<AttendsDTO> attendsDTOList = dateList.stream().map(date -> {
                    AttendsDTO attendsDTO = new AttendsDTO();
                    attendsDTO.setAttendDate(date);
                    // 根据日期从上面的map中获取到该天的打卡数据
                    List<AttendPunch> attendPunches = puchMap.get(date);
                    // 如果不存在，说明当天上午、下午都没打卡，直接设置为缺卡/缺卡
                    if (CollectionUtils.isEmpty(attendPunches)) {
                        attendsDTO.setAttendStatus("缺卡/缺卡");
                    }else {
                        String attendStatus = this.handleAttendStatus(attendPunches);
                        attendsDTO.setAttendStatus(attendStatus);
                    }
                    return attendsDTO;

                }).collect(Collectors.toList());
                attendPunchDTO.setAttendsList(attendsDTOList);

                // 转为AttendPunchDTO
                return attendPunchDTO;
            }).collect(Collectors.toList());
        }
        return null;
    }

    @Override
    public void exportAttendData(String startTime, String endTime) throws IOException {
        // 1.获取考勤列表数据
        List<AttendPunchDTO> punchDTOS = this.members(startTime, endTime);

        // 2.获取响应对象response，设置文件下载的头信息
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletResponse response = servletRequestAttributes.getResponse();
        response.setHeader("Content-disposition", "attachment;filename=Attendance.xls");
        response.setContentType("application/octet-stream;charset=UTF-8");
        response.setCharacterEncoding("utf-8");

        // 3.通过response获取输出流, 并生成的Excel输出
        EasyExcel.write(response.getOutputStream())
                .excelType(ExcelTypeEnum.XLS)
                .head(this.handleHeaders(punchDTOS))
                .registerWriteHandler(this.horizontalCellStyleStrategy())
                .registerWriteHandler(new CustomHandler())
                .sheet("考勤记录")
                .doWrite(this.handleData(punchDTOS));
    }
    /**
     * 处理表头
     */
    private List<List<String>> handleHeaders(List<AttendPunchDTO> punchDTOList){
        List<List<String>> headers = new ArrayList<>();

        headers.add(Arrays.asList("姓名"));
        headers.add(Arrays.asList("工号"));
        headers.add(Arrays.asList("部门"));
        headers.add(Arrays.asList("职位"));
        if (!CollectionUtils.isEmpty(punchDTOList)){
            AttendPunchDTO attendPunchDTO = punchDTOList.get(0);
            List<AttendsDTO> attendsDTOList = attendPunchDTO.getAttendsList();

            if (!CollectionUtils.isEmpty(attendsDTOList)){
                for (AttendsDTO attendsDTO : attendsDTOList) {
                    headers.add(Arrays.asList(attendsDTO.getAttendDate()));
                }
            }
        }
        return headers;
    }
    /**
     * 设置样式
     */
    public HorizontalCellStyleStrategy horizontalCellStyleStrategy() {

        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        //内容策略 - 水平居中
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);

        //设置字体
        WriteFont font = new WriteFont();
        font.setFontName("阿里巴巴普惠体");
        font.setFontHeightInPoints((short) 11);
        contentWriteCellStyle.setWriteFont(font);
        HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(contentWriteCellStyle, contentWriteCellStyle);

        return horizontalCellStyleStrategy;
    }
    /**
     * 处理每行数据
     * @param punchDTOS
     * @return
     */
    private List<List<String>> handleData(List<AttendPunchDTO> punchDTOS) {
        List<List<String>> dataList = new ArrayList<>();

        if (!CollectionUtils.isEmpty(punchDTOS)) {
            for (AttendPunchDTO punchDTO : punchDTOS) {
                // 每一行基本信息
                List<String> rowData = new ArrayList<>();
                rowData.add(punchDTO.getUserName()); // 姓名
                rowData.add(punchDTO.getWorkNumber()); // 工号
                rowData.add(punchDTO.getDepartmentName()); // 部门
                rowData.add(punchDTO.getPost()); // 职位

                // 每一行考勤数据
                List<AttendsDTO> attendsList = punchDTO.getAttendsList();
                if (!CollectionUtils.isEmpty(attendsList)) {
                    for (AttendsDTO attendsDTO : attendsList) {
                        rowData.add(attendsDTO.getAttendStatus());
                    }
                }

                // 将每行数据添加到data中
                dataList.add(rowData);
            }
        }
        return dataList;
    }

    /**
     * 处理每天的打卡信息
     */

    private String handleAttendStatus(List<AttendPunch> attendPunches) {
        String morning = "缺卡";
        String afternoon = "缺卡";
        for (AttendPunch attendPunch : attendPunches) {
            if (attendPunch.getPunchOnOffWork() == AttendEnums.NOON_TYPE_MORNING.value()){
                morning = this.getPuchTypeDesc(attendPunch.getMorningPunchType());
            }else if (attendPunch.getPunchOnOffWork() == AttendEnums.NOON_TYPE_AFTERNOON.value()){
                afternoon = this.getPuchTypeDesc(attendPunch.getAfternoonPunchType());
            }
        }
        return morning + "/" + afternoon;
    }
    /**
     * 根据打卡类型，获取打卡类型描述
     */
    private String getPuchTypeDesc(Integer punchType) {
        String desc = "";
        switch (punchType) {
            case 1:
                desc = AttendEnums.PUNCH_TYPE_OK.toString();
                break;
            case 2:
                desc = AttendEnums.PUNCH_TYPE_LATE.toString();
                break;
            case 3:
                desc = AttendEnums.PUNCH_TYPE_EARLY.toString();
                break;
            case 4:
                desc = AttendEnums.PUNCH_TYPE_STAYALAY.toString();
                break;
            case 5:
                desc = AttendEnums.PUNCH_TYPE_NOT_WORK_DAY.toString();
                break;
            default:
                break;
        }
        return desc;
    }
    /**
     * 设置是否为有效打卡（上午以最早一次为有效，下午以最晚一次为有效）
     * @param attendPunch
     */
    private void setEnable(AttendPunch attendPunch) {
        //根据条件查询数据库中当前用户的今日上午/下午 打卡记录
        LambdaQueryWrapper<AttendPunch> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(AttendPunch::getCompanyUserId, attendPunch.getCompanyUserId()) //打卡员工ID
                .eq(AttendPunch::getPunchDateStr, attendPunch.getPunchDateStr()) //打卡时间
                .eq(AttendPunch::getPunchOnOffWork, attendPunch.getPunchOnOffWork()) //上午 , 下午
                .eq(AttendPunch::getAreaValid, true); //打卡范围有效


        Integer punchCount = attendPunchMapper.selectCount(queryWrapper);
        //如果未获取到, 说明没有打过卡, 此次打卡, 无论是上午还是下午都是有效的
        if (punchCount == 0) {
            attendPunch.setEffectiveValid(true);
        } else {
            //如果获取到记录, 说明已经打过卡
            //如果是上午, 第一条有效, 其余都是无效的
            if (attendPunch.getPunchOnOffWork() == AttendEnums.NOON_TYPE_MORNING.value()) {
                attendPunch.setEffectiveValid(false);

            } else { //如果是下午, 最后一条有效, 其余都是无效的
                attendPunch.setEffectiveValid(true);
                //更新之前的打卡记录为无效, 以最后一次打卡为准
                AttendPunch ap = new AttendPunch();
                ap.setEffectiveValid(false);
                attendPunchMapper.update(ap, queryWrapper);
            }
        }
    }

    /**
     * 设置打卡类型（打卡类型 1正常；2迟到；3早退；4旷工）
     * @param attendPunch
     * @param attendGroupDTO
     */
    private void setPunchType(AttendPunch attendPunch, AttendGroupDTO attendGroupDTO) {
        //上班时间
        String startWorkTime = attendGroupDTO.getStartWorkTime();
        //下班时间
        String offWorkTime = attendGroupDTO.getOffWorkTime();
        //允许迟到分钟
        Integer allowLateMinutes = attendGroupDTO.getAllowLateMinutes();
        //允许旷工分钟
        Integer lateMinutes = attendGroupDTO.getLateMinutes();
        //实际打卡时间
        String realPunchTime = DateTimeUtil.dateToStr(attendPunch.getPunchTime(),DateTimeUtil.TIME_FORMAT_1);
        //默认正常
        AttendEnums punchType = AttendEnums.PUNCH_TYPE_OK;
        //如果是上午打卡
        if (attendPunch.getPunchOnOffWork() == AttendEnums.NOON_TYPE_MORNING.value()){
            if (DateTimeUtil.compareTime(realPunchTime,startWorkTime,allowLateMinutes) == 1){
                punchType = AttendEnums.PUNCH_TYPE_LATE;
                if (DateTimeUtil.compareTime(realPunchTime,startWorkTime,lateMinutes) == 1){
                    punchType = AttendEnums.PUNCH_TYPE_STAYALAY;
                }
            }
            attendPunch.setMorningPunchType(punchType.value());
        }else {
            if (DateTimeUtil.compareTime(offWorkTime, realPunchTime, allowLateMinutes) == 1){
                punchType = AttendEnums.PUNCH_TYPE_EARLY;
                if (DateTimeUtil.compareTime(offWorkTime, realPunchTime, lateMinutes) == 1){
                    punchType = AttendEnums.PUNCH_TYPE_STAYALAY;
                }
            }
            attendPunch.setAfternoonPunchType(punchType.value());
        }

    }

    /**
     * 判断是否是工作日打卡，非工作日抛出异常
     * @param attendGroupDTO
     * @return
     */
    private boolean checkWorkDays(AttendGroupDTO attendGroupDTO) {
        // 使用hutool的工具类，获取今天是周几
        int week = DateUtil.thisDayOfWeek();   //1: 表示周日, 2: 表示周一, 3:表示周二
        List<String> workdays = attendGroupDTO.getWorkdays(); // [1,1,1,1,1,0,0]

        // 获取在集合中的下标
        if (week == 1) {
            week = 6;
        } else {
            week = week - 2;
        }

        Integer value = Integer.valueOf(workdays.get(week)); // 获取workdays集合元素
        return value == 1;
    }

    /**
     * 判断是否有效范围内打卡，非有效范围抛出异常
     */
    public boolean checkLocation(AttendPunchDTO attendPunchDTO,AttendGroupDTO attendGroupDTO){
        //比较"打卡坐标" 和 "考勤组打卡坐标"的距离
        Integer meter = DistanceUtil.getDistanceMeter(attendPunchDTO.getLat().doubleValue(), attendPunchDTO.getLng().doubleValue(), attendGroupDTO.getLat().doubleValue(), attendGroupDTO.getLng().doubleValue());
        //判定是否小于等于考勤组中设置的范围
        return meter <= attendGroupDTO.getAddressRange();
    }

}
